Variant可以通过多种方式进行初始化和赋值，包括默认构造、复制和移动构造以及复制和移动赋值。本节详细介绍了这些Variant操作。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{默认构造}

Variant是否应该提供默认构造函数?若不这样做，因为总是需要一个初始值(即使初始值在编程上没有意义)，所以Variant的使用可能会变得困难。若提供了默认构造函数，那么语义应该是什么?

一种可能的语义是默认初始化没有存储值，由辨别器0表示。空Variant通常没有用(不能访问或找到任何值来提取)，并且将此作为默认初始化行为会将空Variant的异常状态(在第26.4.3节中描述)提升为普通状态。

或者，默认构造函数可以构造某种类型的值。对于我们的Variant，遵循C++17的std::variant<>语义，并默认构造类型列表中的第一个类型的值:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{variant/variantdefaultctor.hpp}
\begin{lstlisting}[style=styleCXX]
template<typename... Types>
Variant<Types...>::Variant() {
	*this = Front<Typelist<Types...>>();
}
\end{lstlisting}

这种方法简单可预测，并避免在大多数使用中引入空Variant。这个行为可以在下面的程序中看到:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{variant/variantdefaultctor.cpp}
\begin{lstlisting}[style=styleCXX]
#include "variant.hpp"
#include <iostream>

int main()
{
	Variant<int, double> v;
	if (v.is<int>()) {
		std::cout << "Default-constructed v stores the int "
		<< v.get<int>() << ’\n’;
	}
	Variant<double, int> v2;
	if (v2.is<double>()) {
		std::cout << "Default-constructed v2 stores the double "
				<< v2.get<double>() << ’\n’;
	}
}
\end{lstlisting}

输出为

\begin{tcblisting}{commandshell={}}
Default-constructed v stores the int 0
Default-constructed v2 stores the double 0
\end{tcblisting}

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{复制/移动构造}

复制和移动构造更有趣。要复制源变量，需要确定当前存储的是哪种类型，将该值复制构造到缓冲区中，并设置辨别器。visit()可以解码源Variant的动态值，而从VariantChoice继承的复制赋值操作符将复制构造一个值到缓冲区:

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}尽管在Lambda表达式中使用了赋值操作符(=)，但赋值操作符在VariantChoice中的实际实现执行的是复制构造，因为该变量最初无存储值。
\end{tcolorbox}

\hspace*{\fill} \\ %插入空行
\noindent
\textit{variant/variantcopyctor.hpp}
\begin{lstlisting}[style=styleCXX]
emplate<typename... Types>
Variant<Types...>::Variant(Variant const& source) {
	if (!source.empty()) {
		source.visit([&](auto const& value) {
						*this = value;
					});
	}
}
\end{lstlisting}

移动构造函数与此类似，不同之处在于访问源变量时使用std::move，并根据源值进行移动赋值:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{variant/variantcopyctor.hpp}
\begin{lstlisting}[style=styleCXX]
template<typename... Types>
Variant<Types...>::Variant(Variant&& source) {
	if (!source.empty()) {
		std::move(source).visit([&](auto&& value) {
									*this = std::move(value);
								});
	}
}
\end{lstlisting}

基于访问器实现的一个有趣的方面是，其也适用于复制和移动操作的模板参数。模板复制构造函数可以这样定义:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{variant/variantcopyctortmpl.hpp}
\begin{lstlisting}[style=styleCXX]
template<typename... Types>
template<typename... SourceTypes>
Variant<Types...>::Variant(Variant<SourceTypes...> const& source) {
	if (!source.empty()) {
		source.visit([&](auto const& value) {
							*this = value;
						});
	}
}
\end{lstlisting}

这段代码访问了source，所以对*this的赋值将在每个SourceTypes中发生。此赋值的重载解析将为每个SourceTypes找到最合适的目标类型，并根据需要进行隐式转换。下面的例子说明了不同Variant类型的构造和赋值:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{variant/variantpromote.hpp}
\begin{lstlisting}[style=styleCXX]
#include "variant.hpp"
#include <iostream>
#include <string>

int main()
{
	Variant<short, float, char const*> v1((short)123);
	
	Variant<int, std::string, double> v2(v1);
	
	std::cout << "v2 contains the integer " << v2.get<int>() << ’\n’;
	
	v1 = 3.14f;
	Variant<double, int, std::string> v3(std::move(v1));
	std::cout << "v3 contains the double " << v3.get<double>() << ’\n’;
	
	v1 = "hello";
	Variant<double, int, std::string> v4(std::move(v1));
	std::cout << "v4 contains the string " << v4.get<std::string>() << ’\n’;
}
\end{lstlisting}

从v1构造或赋值到v2或v3涉及整型提升(short到int)、浮点提升(float到double)和用户定义的转换(char const*到std::string)。

输出如下所示:

\begin{tcblisting}{commandshell={}}
v2 contains the integer 123
v3 contains the double 3.14
v4 contains the string hello
\end{tcblisting}

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{赋值}

Variant赋值操作符类似于上面的复制和移动构造函数。这里，只演示复制赋值操作符:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{variant/variantcopyassign.hpp}
\begin{lstlisting}[style=styleCXX]
template<typename... Types>
Variant<Types...>& Variant<Types...>::operator= (Variant const& source) {
	if (!source.empty()) {
		source.visit([&](auto const& value) {
			*this = value;
		});
	}
	else {
		destroy();
	}
	return *this;
}
\end{lstlisting}

这里唯一有趣的是在else分支中:当source不包含值(由辨别器0表示)时，我们销毁目标Variant的值，隐式地将其辨别器设置为0。


















